{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Path and CrossSection\n",
    "\n",
    "You can create a `Path` in gdsfactory and extrude it with an arbitrary `CrossSection`.\n",
    "\n",
    "Let us create a path:\n",
    "\n",
    "- Create a blank `Path`.\n",
    "- Append points to the `Path` by either using the built-in functions (`arc()`, `straight()`, `euler()` ...) or by providing your own lists of points.\n",
    "- Specify `CrossSection` with layers and offsets.\n",
    "- Extrude `Path` with a `CrossSection` to create a Component with the path polygons in it.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "import gdsfactory as gf"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2",
   "metadata": {},
   "source": [
    "## Path\n",
    "\n",
    "The first step is to generate the list of points we want the path to follow.\n",
    "Let us now start out by creating a blank `Path` and using the built-in functions to\n",
    "make a few smooth turns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3",
   "metadata": {},
   "outputs": [],
   "source": [
    "p1 = gf.path.straight(length=5)\n",
    "\n",
    "# This creates a curved path segment using an Euler bend profile,\n",
    "# which is a curve with a continuously changing radius designed to minimize light loss. This specific bend turns by 45 degrees.\n",
    "# By setting use_eff=False, you are telling the function to ignore complex calculations and instead create a simpler bend with a constant, user-specified radius.\n",
    "p2 = gf.path.euler(radius=5, angle=45, p=0.5, use_eff=False)\n",
    "\n",
    "# The + operator is used to concatenate the two paths.\n",
    "# It takes the second path (p2) and appends it to the end of the first path (p1), ensuring a smooth, continuous transition.\n",
    "p = p1 + p2\n",
    "f = p.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "p1 = gf.path.straight(length=5)\n",
    "p2 = gf.path.euler(radius=5, angle=45, p=0.5, use_eff=False)\n",
    "p = p2 + p1\n",
    "f = p.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Note: -angle rotations correspond to a clockwise turn.\n",
    "P = gf.Path()\n",
    "P += gf.path.arc(radius=10, angle=90)  # Circular arc.\n",
    "P += gf.path.straight(length=10)  # Straight section.\n",
    "P += gf.path.euler(radius=3, angle=-90)  # Euler bend (aka \"racetrack\" curve).\n",
    "P += gf.path.straight(length=40)\n",
    "P += gf.path.arc(radius=8, angle=-45)\n",
    "P += gf.path.straight(length=10)\n",
    "P += gf.path.arc(radius=8, angle=45)\n",
    "P += gf.path.straight(length=10)\n",
    "\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6",
   "metadata": {},
   "outputs": [],
   "source": [
    "p2 = P.copy().rotate(45)\n",
    "f = p2.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "P.points - p2.points"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "You can also modify our path in the same ways as any other gdsfactory object:\n",
    "\n",
    "- Manipulation with `move()`, `rotate()`, `mirror()`, etc\n",
    "- Accessing properties like `xmin`, `y`, `center`, `bbox`, etc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "P.movey(10)\n",
    "P.xmin = 20\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10",
   "metadata": {},
   "source": [
    "You can also check the length of the curve with the `length()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "P.length()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12",
   "metadata": {},
   "source": [
    "## CrossSection\n",
    "\n",
    "Now that you have got your path defined, the next step is to define the cross-section of the path. To do this, you can create a blank `CrossSection` and add whatever cross-sections you want to it.\n",
    "You can then combine the `Path` and the `CrossSection` using the `gf.path.extrude()` function to generate a component:\n",
    "\n",
    "\n",
    "### Option 1: Single layer and width cross-section\n",
    "\n",
    "The simplest option is to just set the cross-section to be a constant width by passing a number to `extrude()` like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Extrude the Path and the cross-section.\n",
    "# The extrude function converts a 1D path into a 2D shape by giving it a specified width.\n",
    "\n",
    "c = gf.path.extrude(P, layer=(1, 0), width=1.5)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14",
   "metadata": {},
   "source": [
    "### Option 2: Arbitrary Cross-section\n",
    "\n",
    "You can also extrude an arbitrary cross_section."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "Now, what if we want a more complicated straight?  For instance, in some\n",
    "photonic applications it is helpful to have a shallow etch that appears on either\n",
    "side of the straight (often called a trench or sleeve).  Additionally, it might be nice\n",
    "to have a port on either end of the center section so we can snap other\n",
    "geometries to it.  Let us try adding something like that in:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.straight()\n",
    "\n",
    "# The code first defines three gf.Section objects, each representing a part of the total cross-section.\n",
    "# s0: The central core section. It is 1 µm wide, centered at an offset of 0, and is on layer (1, 0).\n",
    "# s1: A side section. It is 2 µm wide, its center is offset by +2 µm from the main centerline, and it is on layer (2, 0).\n",
    "# s2: Another side section, identical to s1 but offset by -2 µm.\n",
    "s0 = gf.Section(width=1, offset=0, layer=(1, 0), port_names=(\"in\", \"out\"))\n",
    "s1 = gf.Section(width=2, offset=2, layer=(2, 0))\n",
    "s2 = gf.Section(width=2, offset=-2, layer=(2, 0))\n",
    "x = gf.CrossSection(sections=(s0, s1, s2))\n",
    "\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "c.draw_ports()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17",
   "metadata": {},
   "source": [
    "If you add more ports to a cross-section it also exposes its ports."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.straight()\n",
    "\n",
    "# Add a few \"sections\" to the cross-section.\n",
    "s0 = gf.Section(width=1, offset=0, layer=(1, 0), port_names=(\"in\", \"out\"))\n",
    "s1 = gf.Section(width=2, offset=2, layer=(2, 0), port_names=(\"e1\", \"e2\"))\n",
    "s2 = gf.Section(width=2, offset=-2, layer=(2, 0))\n",
    "x = gf.CrossSection(sections=(s0, s1, s2))\n",
    "\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "c.draw_ports()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.arc() # A 1D path in the shape of a 90-degree circular arc is created. This defines the centerline for the extrusion.\n",
    "\n",
    "# Combine the Path and the cross-section.\n",
    "b = gf.path.extrude(p, cross_section=x)\n",
    "b.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "⚠️ Warning! for GS routing. You need to add a centered port for routing to work correctly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.straight()\n",
    "\n",
    "# Add GS routing sections.\n",
    "# GS routing is a specific auto-routing algorithm used in gdsfactory to create smooth, low-loss waveguide connections between component ports.\n",
    "s0 = gf.Section(width=2, offset=0, layer=(2, 0), port_names=(\"g1\", \"g2\"))\n",
    "s1 = gf.Section(width=2, offset=4, layer=(2, 0))\n",
    "x = gf.CrossSection(sections=(s0, s1), radius=8)\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "pad = c\n",
    "c_copy = c.copy()\n",
    "c_copy.draw_ports()\n",
    "c_copy.plot()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Do not do this for GS routing. See solution below.\n",
    "c2 = gf.Component()\n",
    "pad1 = c2 << pad\n",
    "pad2 = c2 << pad\n",
    "pad2.move((100, 100))\n",
    "\n",
    "\n",
    "# The gf.routing.route_bundle function is a powerful auto-router for creating multiple, parallel waveguide connections.\n",
    "# [pad1.ports[\"g2\"]]: A list of the starting ports for the routes.\n",
    "# [pad2.ports[\"g1\"]]: A list of the ending ports for the routes.\n",
    "# sort_ports=True: An option that helps the router find the optimal, non-crossing paths when routing multiple waveguides.\n",
    "# bend='bend_euler': Specifies that any curves in the route should be smooth Euler bends.\n",
    "gf.routing.route_bundle(c2, [pad1.ports[\"g2\"]], [pad2.ports[\"g1\"]], cross_section=x, sort_ports=True, bend='bend_euler')\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23",
   "metadata": {},
   "source": [
    "For GSG routing it works well because the port is at the center. GSG routing is a specific auto-routing algorithm used in gdsfactory to create smooth, S-bend-like waveguide connections between component ports."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.straight()\n",
    "\n",
    "# Add a few \"sections\" to the cross-section\n",
    "g = gf.Section(width=2, offset=0, layer=(2, 0), port_names=(\"e1\", \"e2\"), port_types=('electrical', 'electrical'))\n",
    "s0 = gf.Section(width=2, offset=-4, layer=(2, 0))\n",
    "s1 = gf.Section(width=2, offset=4, layer=(2, 0))\n",
    "x = gf.CrossSection(sections=(g, s0, s1), radius=8)\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "c_copy = c.copy()\n",
    "c_copy.draw_ports()\n",
    "c_copy.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "c2 = gf.Component()\n",
    "pad1 = c2 << c\n",
    "pad2 = c2 << c\n",
    "pad2.move((100, 100))\n",
    "gf.routing.route_bundle(c2, [pad1.ports[\"e2\"]], [pad2.ports[\"e1\"]], cross_section=x, port_type='electrical')\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26",
   "metadata": {},
   "source": [
    "For GS routing the recommended solution is adding a dummy / abstract layer in the middle, where we add the ports."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "27",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.straight()\n",
    "\n",
    "# Add GS routing sections.\n",
    "# 99, 0 is an abstract layer that can be used to add ports to the path.\n",
    "s0 = gf.Section(width=2, offset=0, layer=(99, 0), port_names=(\"e1\", \"e2\"))\n",
    "s1 = gf.Section(width=2, offset=-4, layer=(2, 0))\n",
    "s2 = gf.Section(width=2, offset=+4, layer=(2, 0))\n",
    "x = gf.CrossSection(sections=(s0, s1, s2), radius=8)\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "pad = c\n",
    "c_copy = c.copy()\n",
    "c_copy.draw_ports()\n",
    "c_copy.plot()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28",
   "metadata": {},
   "outputs": [],
   "source": [
    "c2 = gf.Component()\n",
    "pad1 = c2 << c\n",
    "pad2 = c2 << c\n",
    "pad2.move((100, 100))\n",
    "gf.routing.route_bundle(c2,\n",
    "    [pad1.ports[\"e2\"]],\n",
    "    [pad2.ports[\"e1\"]],\n",
    "    cross_section=x,\n",
    "    port_type='optical',\n",
    "    bend='bend_euler',\n",
    "    raise_on_error=True,\n",
    ")\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29",
   "metadata": {},
   "source": [
    "### Option 3: Cross-section with ComponentAlongPath\n",
    "\n",
    "You can also place components along a path, which is useful for wiring vias. A via is a vertical electrical connection that goes through the insulating layers of an integrated circuit to connect different layers of horizontal metal wiring."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "30",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.cross_section import ComponentAlongPath\n",
    "\n",
    "# Create the path.\n",
    "p = gf.path.straight()\n",
    "p += gf.path.arc(10)\n",
    "p += gf.path.straight()\n",
    "\n",
    "# Define a cross-section containing a via.\n",
    "via = ComponentAlongPath(\n",
    "    component=gf.c.rectangle(size=(1, 1), centered=True), spacing=5, padding=2\n",
    ")\n",
    "s = gf.Section(width=0.5, offset=0, layer=(1, 0), port_names=(\"in\", \"out\"))\n",
    "x = gf.CrossSection(sections=(s,), components_along_path=(via,))\n",
    "\n",
    "# Combine the path with the cross-section.\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.cross_section import ComponentAlongPath\n",
    "\n",
    "# Create the path.\n",
    "p = gf.path.straight()\n",
    "p += gf.path.arc(10)\n",
    "p += gf.path.straight()\n",
    "\n",
    "# Define a cross-section with a via.\n",
    "via0 = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=0)\n",
    "viap = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=+2)\n",
    "vian = ComponentAlongPath(component=gf.c.via1(), spacing=5, padding=2, offset=-2)\n",
    "x = gf.CrossSection(sections=[s], components_along_path=(via0, viap, vian))\n",
    "\n",
    "# Combine the path with the cross-section.\n",
    "c = gf.path.extrude(p, cross_section=x)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32",
   "metadata": {},
   "source": [
    "## Path\n",
    "\n",
    "You can pass `append()` lists of path segments.  This makes it easy to combine paths very quickly.\n",
    "Below we show 3 examples using this functionality:\n",
    "\n",
    "**Example 1:** Assemble a complex path by making a list of paths and passing it to `append()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "P = gf.Path()\n",
    "\n",
    "# Create the basic Path components.\n",
    "left_turn = gf.path.euler(radius=4, angle=90)\n",
    "right_turn = gf.path.euler(radius=4, angle=-90)\n",
    "straight = gf.path.straight(length=10)\n",
    "\n",
    "# Assemble a complex path by making a list of paths and passing it to `append()`.\n",
    "# .append([...]): This method takes a list of path factories and adds them sequentially to the end of the existing path P.\n",
    "# Each new segment starts where the previous one ended, creating a single, continuous path.\n",
    "P.append(\n",
    "    [\n",
    "        straight,\n",
    "        left_turn,\n",
    "        straight,\n",
    "        right_turn,\n",
    "        straight,\n",
    "        straight,\n",
    "        right_turn,\n",
    "        left_turn,\n",
    "        straight,\n",
    "    ]\n",
    ")\n",
    "\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = (\n",
    "    straight\n",
    "    + left_turn\n",
    "    + straight\n",
    "    + right_turn\n",
    "    + straight\n",
    "    + straight\n",
    "    + right_turn\n",
    "    + left_turn\n",
    "    + straight\n",
    ")\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35",
   "metadata": {},
   "source": [
    "**Example 2:** Create an \"S-turn\" just by making a list of `[left_turn,\n",
    "right_turn]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.Path()\n",
    "\n",
    "# Create an \"S-turn\" by making a list.\n",
    "s_turn = [left_turn, right_turn]\n",
    "\n",
    "P.append(s_turn)\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37",
   "metadata": {},
   "source": [
    "**Example 3:** Repeat the S-turn 3 times by nesting our S-turn list in another list. Nesting means placing one data structure inside another of the same type. In this context, it means creating a \"list of lists.\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "38",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.Path()\n",
    "\n",
    "# Create an \"S-turn\" using a list.\n",
    "s_turn = [left_turn, right_turn]\n",
    "\n",
    "# Repeat the S-turn 3 times by nesting our S-turn list 3x times in another list.\n",
    "triple_s_turn = [s_turn, s_turn, s_turn]\n",
    "\n",
    "P.append(triple_s_turn)\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39",
   "metadata": {},
   "source": [
    "Note you can also use the Path() constructor to immediately construct your Path:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.Path([straight, left_turn, straight, right_turn, straight])\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41",
   "metadata": {},
   "source": [
    "## Waypoint smooth paths\n",
    "\n",
    "You can also build smooth paths between waypoints with the `smooth()` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "42",
   "metadata": {},
   "outputs": [],
   "source": [
    "points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])\n",
    "plt.plot(points[:, 0], points[:, 1], \".-\")\n",
    "\n",
    "# This functionensures that one unit on the x-axis is the same length as one unit on the y-axis.\n",
    "# This is crucial for plots where the geometric shape is important.\n",
    "plt.axis(\"equal\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43",
   "metadata": {},
   "outputs": [],
   "source": [
    "points = np.array([(20, 10), (40, 10), (20, 40), (50, 40), (50, 20), (70, 20)])\n",
    "\n",
    "P = gf.path.smooth(\n",
    "    points=points,\n",
    "    radius=2,\n",
    "    bend=gf.path.euler,  # Alternatively, use pp.arc, which will create a constant-radius bend.\n",
    "    use_eff=False,\n",
    ")\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "44",
   "metadata": {},
   "source": [
    "## Waypoint sharp paths\n",
    "\n",
    "It is also possible to make more traditional angular paths (e.g. electrical wires) in a few different ways.\n",
    "\n",
    "**Example 1:** Using a simple list of points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.Path([(20, 10), (30, 10), (40, 30), (50, 30), (50, 20), (70, 20)])\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46",
   "metadata": {},
   "source": [
    "**Example 2:** Using the \"turn and move\" method, where you manipulate the end angle of the path so that when you append points to it they are in the correct direction.  *Note: It is crucial that the number of points per straight section is set to 2 (`gf.path.straight(length, num_pts = 2)`) otherwise the extrusion algorithm will show defects.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.Path()\n",
    "P += gf.path.straight(length=10, npoints=2)\n",
    "P.end_angle += 90  # \"Turn\" 90 deg (left).\n",
    "P += gf.path.straight(length=10, npoints=2)  # \"Walk\" length of 10.\n",
    "P.end_angle += -135  # \"Turn\" -135 degrees (right).\n",
    "P += gf.path.straight(length=15, npoints=2)  # \"Walk\" length of 15.\n",
    "P.end_angle = 0  # Force the direction to be 0 degrees.\n",
    "P += gf.path.straight(length=10, npoints=2) \n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "48",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "s0 = gf.Section(width=1, offset=0, layer=(1, 0))\n",
    "s1 = gf.Section(width=1.5, offset=2.5, layer=(2, 0))\n",
    "s2 = gf.Section(width=1.5, offset=-2.5, layer=(3, 0))\n",
    "X = gf.CrossSection(sections=[s0, s1, s2])\n",
    "c = gf.path.extrude(P, X)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "## Custom curves\n",
    "\n",
    "Now let us have some fun and try to make a loop-de-loop structure with parallel\n",
    "straights and several ports.\n",
    "\n",
    "To create a new type of curve we simply make a function that produces an array\n",
    "of points. The best way to do that is to create a function which allows you to\n",
    "specify a large number of points along that curve -- in the case shown below, the\n",
    "`looploop()` function outputs 1000 points along a looping path.  Later, if we\n",
    "want to reduce the number of points in our geometry we can easily `simplify` the\n",
    "path.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50",
   "metadata": {},
   "outputs": [],
   "source": [
    "def looploop(num_pts=1000):\n",
    "    \"\"\"Simple limacon looping curve.\"\"\"\n",
    "\n",
    "    # This line creates an array of num_pts evenly spaced numbers ranging from -π to 0. This array represents the angle t in polar coordinates.\n",
    "    t = np.linspace(-np.pi, 0, num_pts) \n",
    "    r = 20 + 25 * np.sin(t) # This line calculates the radius r for each corresponding angle t using the polar equation for a limaçon curve.\n",
    "\n",
    "    # # These lines convert the polar coordinates (r, t) into standard Cartesian coordinates (x, y), which are needed for plotting.\n",
    "    x = r * np.cos(t) \n",
    "    y = r * np.sin(t) \n",
    "\n",
    "    # The separate x and y arrays are combined into a single NumPy array of coordinate pairs, which is then returned by the function.\n",
    "    return np.array((x, y)).T\n",
    "\n",
    "\n",
    "# Create the path points.\n",
    "P = gf.Path()\n",
    "P.append(gf.path.arc(radius=10, angle=90))\n",
    "P.append(gf.path.straight())\n",
    "P.append(gf.path.arc(radius=5, angle=-90))\n",
    "P.append(looploop(num_pts=1000))\n",
    "P.rotate(-45)\n",
    "\n",
    "# Create the cross-section.\n",
    "s0 = gf.Section(width=1, offset=0, layer=(1, 0), port_names=(\"in\", \"out\"))\n",
    "s1 = gf.Section(width=0.5, offset=2, layer=(2, 0))\n",
    "s2 = gf.Section(width=0.5, offset=4, layer=(3, 0))\n",
    "s3 = gf.Section(width=1, offset=0, layer=(4, 0))\n",
    "X = gf.CrossSection(sections=(s0, s1, s2, s3))\n",
    "\n",
    "c = gf.path.extrude(P, X)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51",
   "metadata": {},
   "source": [
    "You can create Paths from any array of points -- just be sure that they form\n",
    "smooth curves!  If we examine our path `P` we can see that we have effortlessly\n",
    "created a long list of points:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "52",
   "metadata": {},
   "outputs": [],
   "source": [
    "path_points = P.points  # Curve points are stored as a numpy array in P.points.\n",
    "print(np.shape(path_points))  # The shape of the array is Nx2.\n",
    "print(len(P))  # Equivalently, use len(P) to see how many points are inside."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53",
   "metadata": {},
   "source": [
    "## Simplifying / reducing point usage\n",
    "\n",
    "One of the primary concerns of generating smooth curves is that too many points\n",
    "are generated, inflating file sizes and making boolean operations\n",
    "computationally expensive. Fortunately, PHIDL has a fast implementation of the\n",
    "[Ramer-Douglas–Peucker\n",
    "algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm)\n",
    "that lets you reduce the number of points in a curve without changing its shape.\n",
    "All that needs to be done when you make a `component()` is extruding the path with a cross_section, you need to specify the\n",
    "`simplify` argument.\n",
    "\n",
    "If we specify `simplify = 1e-3`, the number of points in the line drops from\n",
    "12,000 to 4,000, and the remaining points form a line that is identical to\n",
    "within `1e-3` distance from the original (for the default 1 micron unit size,\n",
    "this corresponds to 1 nanometer resolution):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "54",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The remaining points form a identical line to within `1e-3` from the original.\n",
    "c = gf.path.extrude(p=P, cross_section=X, simplify=1e-3)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55",
   "metadata": {},
   "source": [
    "Let us say we need fewer points.  We can increase the `simplify` tolerance by specifying `simplify = 1e-1`.  This drops the number of points to ~400 points and they form a line that is identical to within `1e-1` distance from the original:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.path.extrude(P, cross_section=X, simplify=1e-1)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "57",
   "metadata": {},
   "source": [
    "Taken to absurdity, what happens if we set `simplify = 0.3`?  Once again, the\n",
    "~200 remaining points form a line that is within `0.3` units from the original\n",
    "-- but that line will look pretty bad."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.path.extrude(P, cross_section=X, simplify=0.3)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "59",
   "metadata": {},
   "source": [
    "## Curvature calculation\n",
    "\n",
    "The `Path` class has a `curvature()` method that computes the curvature `K` of\n",
    "your smooth path (K = 1/(radius of curvature)).  This can be helpful for\n",
    "verifying that your curves transition smoothly such as in [track-transition\n",
    "curves](https://en.wikipedia.org/wiki/Track_transition_curve) (also known as\n",
    "\"Euler\" bends in the photonics world). Euler bends have lower mode-mismatch loss as explained in [this paper](https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-27-22-31394&id=422321)\n",
    "\n",
    "Note this curvature is numerically computed, so areas in which the curvature jumps\n",
    "instantaneously (such as between an arc and a straight segment) will be slightly\n",
    "interpolated, and sudden changes in point density along the curve can cause\n",
    "discontinuities."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "60",
   "metadata": {},
   "outputs": [],
   "source": [
    "straight_points = 100\n",
    "\n",
    "P = gf.Path()\n",
    "P.append(\n",
    "    [\n",
    "        gf.path.straight(\n",
    "            length=10, npoints=straight_points\n",
    "        ),  # Should have a curvature of 0\n",
    "        gf.path.euler(\n",
    "            radius=3, angle=90, p=0.5, use_eff=False\n",
    "        ),  # Euler straight-to-bend transition with min. bend radius of 3 (max curvature of 1/3)\n",
    "        gf.path.straight(\n",
    "            length=10, npoints=straight_points\n",
    "        ),  # Should have a curvature of 0\n",
    "        gf.path.arc(radius=10, angle=90),  # Should have a curvature of 1/10\n",
    "        gf.path.arc(radius=5, angle=-90),  # Should have a curvature of -1/5\n",
    "        gf.path.straight(\n",
    "            length=2, npoints=straight_points\n",
    "        ),  # Should have a curvature of 0\n",
    "    ]\n",
    ")\n",
    "\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61",
   "metadata": {},
   "source": [
    "Arc paths are equivalent to `bend_circular` and euler paths are equivalent to `bend_euler`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "62",
   "metadata": {},
   "outputs": [],
   "source": [
    "# The .curvature() method of the Path object P is called.\n",
    "# It returns two arrays: s, which contains the cumulative distance (arc length) at each point along the path,\n",
    "# and K, which contains the corresponding curvature at that point. (Curvature is the reciprocal of the bend radius).\n",
    "s, K = P.curvature()\n",
    "\n",
    "# This plots the arc length s on the x-axis and the curvature K on the y-axis.\n",
    "# The \".-\" format string specifies that the plot should be a line with a dot marker at each data point.\n",
    "plt.plot(s, K, \".-\")\n",
    "plt.xlabel(\"Position along curve (arc length)\")\n",
    "plt.ylabel(\"Curvature\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "63",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.path.euler(radius=3, angle=90, p=1.0, use_eff=False)\n",
    "P.append(gf.path.euler(radius=3, angle=90, p=0.2, use_eff=False))\n",
    "P.append(gf.path.euler(radius=3, angle=90, p=0.0, use_eff=False))\n",
    "P.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64",
   "metadata": {},
   "outputs": [],
   "source": [
    "s, K = P.curvature()\n",
    "plt.plot(s, K, \".-\")\n",
    "plt.xlabel(\"Position along curve (arc length)\")\n",
    "plt.ylabel(\"Curvature\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65",
   "metadata": {},
   "source": [
    "You can compare two 90 degrees euler bends with 180 euler bend.\n",
    "\n",
    "A 180 euler bend is shorter, and has less loss than two 90 degrees euler bend."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "66",
   "metadata": {},
   "outputs": [],
   "source": [
    "straight_points = 100\n",
    "\n",
    "P = gf.Path()\n",
    "P.append(\n",
    "    [\n",
    "        gf.path.euler(radius=3, angle=90, p=1, use_eff=False),\n",
    "        gf.path.euler(radius=3, angle=90, p=1, use_eff=False),\n",
    "        gf.path.straight(length=6, npoints=100),\n",
    "        gf.path.euler(radius=3, angle=180, p=1, use_eff=False),\n",
    "    ]\n",
    ")\n",
    "\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "67",
   "metadata": {},
   "outputs": [],
   "source": [
    "s, K = P.curvature()\n",
    "plt.plot(s, K, \".-\")\n",
    "plt.xlabel(\"Position along curve (arc length)\")\n",
    "plt.ylabel(\"Curvature\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68",
   "metadata": {},
   "source": [
    "## Transitioning between cross-sections\n",
    "\n",
    "Often a critical element of building paths is being able to transition between\n",
    "cross-sections.  You can use the `transition()` function to do exactly this: You\n",
    "simply feed it two `CrossSection`s and it will output a new `CrossSection` that\n",
    "smoothly transitions between the two.\n",
    "\n",
    "Let us start off by creating two cross-sections we want to transition between.\n",
    "Note we give all the cross-sectional elements names by specifying the `name`\n",
    "argument in the `add()` function -- this is important because the transition\n",
    "function will try to match names between the two input cross-sections, and any\n",
    "names not present in both inputs will be skipped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "69",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create our first Cross-section.\n",
    "import gdsfactory as gf\n",
    "\n",
    "s0 = gf.Section(width=1.2, offset=0, layer=(2, 0), name=\"core\", port_names=(\"o1\", \"o2\"))\n",
    "s1 = gf.Section(width=2.2, offset=0, layer=(3, 0), name=\"etch\")\n",
    "s2 = gf.Section(width=1.1, offset=3, layer=(1, 0), name=\"wg2\")\n",
    "X1 = gf.CrossSection(sections=[s0, s1, s2])\n",
    "\n",
    "# Create the second Cross-section that we want to transition to.\n",
    "s0 = gf.Section(width=1, offset=0, layer=(2, 0), name=\"core\", port_names=(\"o1\", \"o2\"))\n",
    "s1 = gf.Section(width=3.5, offset=0, layer=(3, 0), name=\"etch\")\n",
    "s2 = gf.Section(width=3, offset=5, layer=(1, 0), name=\"wg2\")\n",
    "X2 = gf.CrossSection(sections=[s0, s1, s2])\n",
    "\n",
    "# To show the cross-sections, let us now create two paths and create components by extruding them.\n",
    "P1 = gf.path.straight(length=5)\n",
    "P2 = gf.path.straight(length=5)\n",
    "wg1 = gf.path.extrude(P1, X1)\n",
    "wg2 = gf.path.extrude(P2, X2)\n",
    "\n",
    "# Place both cross-section components and quickplot them,\n",
    "# Quickplot is designed to create a wide variety of complex graphs with a simple, concise syntax, making it ideal for quick data exploration.\n",
    "c = gf.Component()\n",
    "wg1ref = c << wg1\n",
    "wg2ref = c << wg2\n",
    "wg2ref.movex(7.5)\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70",
   "metadata": {},
   "source": [
    "Now we can create the transitional cross-section by calling a `transition()` with\n",
    "these two cross-sections as the input. If we want the width to vary as a smooth\n",
    "sinusoid between the sections, we can set `width_type` to `'sine'`\n",
    "(alternatively we could also use `'linear'`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the transitional cross-section.\n",
    "Xtrans = gf.path.transition(cross_section1=X1, cross_section2=X2, width_type=\"sine\")\n",
    "\n",
    "# Create a Path for the transitional cross-section to follow.\n",
    "P3 = gf.path.straight(length=15, npoints=100)\n",
    "\n",
    "# Use the transitional cross-section to create a component.\n",
    "straight_transition = gf.path.extrude_transition(P3, Xtrans)\n",
    "straight_transition.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72",
   "metadata": {},
   "source": [
    "Now that we have all of our components, let us proceed to `connect()` everything and see\n",
    "what it looks like:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.Component(\"transition_demo\")\n",
    "\n",
    "wg1ref = c << wg1\n",
    "wgtref = c << straight_transition\n",
    "wg2ref = c << wg2\n",
    "\n",
    "wgtref.connect(\"o1\", wg1ref.ports[\"o2\"])\n",
    "wg2ref.connect(\"o1\", wgtref.ports[\"o2\"])\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74",
   "metadata": {},
   "source": [
    "Note that since `transition()` outputs a `Transition`, we can make the transition follow an arbitrary path:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "# Transition along a curving path.\n",
    "P4 = gf.path.euler(radius=25, angle=45, p=0.5, use_eff=False)\n",
    "wg_trans = gf.path.extrude_transition(P4, Xtrans)\n",
    "\n",
    "c = gf.Component(\"demo_transition\")\n",
    "wg1_ref = c << wg1  # First cross-section component.\n",
    "wg2_ref = c << wg2\n",
    "wgt_ref = c << wg_trans\n",
    "\n",
    "wgt_ref.connect(\"o1\", wg1_ref.ports[\"o2\"])\n",
    "wg2_ref.connect(\"o1\", wgt_ref.ports[\"o2\"])\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76",
   "metadata": {},
   "source": [
    "You can also extrude an arbitrary transition:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "77",
   "metadata": {},
   "outputs": [],
   "source": [
    "w1 = 1\n",
    "w2 = 5\n",
    "x1 = gf.get_cross_section(\"strip\", width=w1)\n",
    "x2 = gf.get_cross_section(\"strip\", width=w2)\n",
    "transition = gf.path.transition(x1, x2)\n",
    "p = gf.path.arc(radius=10)\n",
    "c = gf.path.extrude_transition(p, transition)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "78",
   "metadata": {},
   "source": [
    "### Asymmetric transition\n",
    "\n",
    "In some cases, you may want the edges of the transition to follow a different function.\n",
    "This can be done by using the `transition_asymmetric()` function.\n",
    "In this case, the argument `width_type` of `transition` is split into `width_type1`, corresponding to the lower edge, and `width_type2`, corresponding to the upper edge of the transition.\n",
    "As in the case of `transition()`, the user can define their own transition function.\n",
    "\n",
    "Let us look at an example where the upper edge follows the sinusoidal (default) transition of the width, while the lower follows a user-defined polynomial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "# Define a custom polynomial transition function from y1 -> y2, for t ∈ [0,1].\n",
    "def polynomial(t: float, y1: float, y2: float) -> float:\n",
    "        return (y2 - y1) * t**3 + y1\n",
    "\n",
    "w1 = 2\n",
    "w2 = 6\n",
    "length = 10\n",
    "cs1 = gf.get_cross_section(\"strip\", width=w1)\n",
    "cs2 = gf.get_cross_section(\"strip\", width=w2)\n",
    "\n",
    "transition = gf.path.transition_asymmetric(\n",
    "    cs1, cs2, width_type1=polynomial, width_type2=\"sine\")\n",
    "p = gf.path.straight(length, npoints=100)\n",
    "c = gf.path.extrude_transition(p, transition)\n",
    "\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "## Variable width / offset\n",
    "\n",
    "In some instances, you may want to vary the width or offset of the path's cross-section as it travels.\n",
    "This can be accomplished by giving the `CrossSection`\n",
    "arguments that are functions or lists.  Let us say we wanted a width that varies\n",
    "sinusoidally along the length of the Path.  To do this, we need to make a width\n",
    "function that is parameterized from 0 to 1: for an example function\n",
    "`my_width_fun(t)` where the width at `t==0` is the width at the beginning of the\n",
    "path and the width at `t==1` is the width at the end.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "81",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import gdsfactory as gf\n",
    "\n",
    "\n",
    "def my_custom_width_fun(t):\n",
    "    # Note: Custom width/offset functions MUST be vectorizable --\n",
    "    # you must be able to call them with an array input like my_custom_width_fun([0, 0.1, 0.2, 0.3, 0.4]).\n",
    "    num_periods = 5\n",
    "\n",
    "    # np.cos(...): This is the core cosine function from the NumPy library, which generates a wave that oscillates between -1 and 1.\n",
    "    # 2 * np.pi * t * num_periods: This part calculates the angle (in radians) for the cosine function.\n",
    "    # It determines the frequency of the wave, i.e., how many full cycles (num_periods) it completes over a given time t.\n",
    "    # This adds a vertical offset of 3 to the wave. Instead of oscillating between -1 and 1, the wave now oscillates between 2 (3 - 1) and 4 (3 + 1).\n",
    "    return 3 + np.cos(2 * np.pi * t * num_periods)\n",
    "\n",
    "\n",
    "P = gf.path.straight(length=40, npoints=30)\n",
    "\n",
    "#Create two cross-sections: one fixed width, one modulated by my_custom_offset_fun.\n",
    "s0 = gf.Section(width=3, offset=-6, layer=(2, 0))\n",
    "s1 = gf.Section(width=0, width_function=my_custom_width_fun, offset=0, layer=(1, 0))\n",
    "X = gf.CrossSection(sections=(s0, s1))\n",
    "\n",
    "# # Extrude the path to create the component.\n",
    "c = gf.path.extrude(P, cross_section=X)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "We can do the same thing with the offset argument:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "def my_custom_offset_fun(t):\n",
    "    num_periods = 3\n",
    "    return 3 + np.cos(2 * np.pi * t * num_periods)\n",
    "\n",
    "\n",
    "P = gf.path.straight(length=40, npoints=30)\n",
    "\n",
    "s0 = gf.Section(width=1, offset=0, layer=(1, 0))\n",
    "s1 = gf.Section(\n",
    "    width=1,\n",
    "    offset_function=my_custom_offset_fun,\n",
    "    layer=(2, 0),\n",
    "    port_names=(\"clad1\", \"clad2\"),\n",
    ")\n",
    "X = gf.CrossSection(sections=(s0, s1))\n",
    "\n",
    "c = gf.path.extrude(P, cross_section=X)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "84",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "source": [
    "## Offsetting a Path\n",
    "\n",
    "Sometimes it is convenient to start with a simple path and offset the line it\n",
    "follows to suit your needs (without using a custom-offset cross-section). Here,\n",
    "we start with two copies of a simple straight path and use the `offset()`\n",
    "function to directly modify each path.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85",
   "metadata": {},
   "outputs": [],
   "source": [
    "def my_custom_offset_fun(t):\n",
    "\n",
    "    num_periods = 3\n",
    "    return 2 + np.cos(2 * np.pi * t * num_periods)\n",
    "\n",
    "\n",
    "P1 = gf.path.straight(npoints=101)\n",
    "P1.offset(offset=my_custom_offset_fun)\n",
    "f = P1.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86",
   "metadata": {},
   "outputs": [],
   "source": [
    "P2 = P1.copy()  # Make a copy of the path.\n",
    "P2.mirror((1, 0))  # Mirror across X-axis.\n",
    "f2 = P2.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.path.arc(radius=10, angle=45)\n",
    "\n",
    "s0 = gf.Section(width=1, offset=3, layer=(2, 0), name=\"waveguide\")\n",
    "s1 = gf.Section(width=1, offset=0, layer=(1, 0), name=\"heater\", port_names=(\"o1\", \"o2\"))\n",
    "X = gf.CrossSection(sections=(s0, s1))\n",
    "c = gf.path.extrude(P, X)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88",
   "metadata": {},
   "outputs": [],
   "source": [
    "P = gf.Path()\n",
    "P.append(gf.path.arc(radius=10, angle=90))  # Circular arc.\n",
    "P.append(gf.path.straight(length=10))  # Straight section.\n",
    "P.append(gf.path.euler(radius=3, angle=-90))  # Euler bend (aka \"racetrack\" curve).\n",
    "P.append(gf.path.straight(length=40))\n",
    "P.append(gf.path.arc(radius=8, angle=-45))\n",
    "P.append(gf.path.straight(length=10))\n",
    "P.append(gf.path.arc(radius=8, angle=45))\n",
    "P.append(gf.path.straight(length=10))\n",
    "\n",
    "f = P.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.path.extrude(P, width=1, layer=(2, 0))\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "90",
   "metadata": {},
   "outputs": [],
   "source": [
    "s0 = gf.Section(width=2, offset=0, layer=(2, 0))\n",
    "xs = gf.CrossSection(sections=(s0,))\n",
    "c = gf.path.extrude(P, xs)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "91",
   "metadata": {},
   "outputs": [],
   "source": [
    "p = gf.path.straight(length=10, npoints=101)\n",
    "s0 = gf.Section(width=1, offset=0, layer=(1, 0), port_names=(\"o1\", \"o2\"), name=\"core\")\n",
    "s1 = gf.Section(width=3, offset=0, layer=(3, 0), name=\"slab\")\n",
    "x1 = gf.CrossSection(sections=(s0, s1))\n",
    "c = gf.path.extrude(p, x1)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "92",
   "metadata": {},
   "outputs": [],
   "source": [
    "s0 = gf.Section(\n",
    "    width=1 + 3, offset=0, layer=(1, 0), port_names=(\"o1\", \"o2\"), name=\"core\"\n",
    ")\n",
    "s1 = gf.Section(width=3 + 3, offset=0, layer=(3, 0), name=\"slab\")\n",
    "x2 = gf.CrossSection(sections=(s0, s1))\n",
    "c2 = gf.path.extrude(p, x2)\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "93",
   "metadata": {},
   "outputs": [],
   "source": [
    "t = gf.path.transition(x1, x2)\n",
    "c3 = gf.path.extrude_transition(p, t)\n",
    "c3.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "94",
   "metadata": {},
   "outputs": [],
   "source": [
    "c4 = gf.Component()\n",
    "start_ref = c4 << c\n",
    "trans_ref = c4 << c3\n",
    "end_ref = c4 << c2\n",
    "\n",
    "trans_ref.connect(\"o1\", start_ref.ports[\"o2\"])\n",
    "end_ref.connect(\"o1\", trans_ref.ports[\"o2\"])\n",
    "c4.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95",
   "metadata": {},
   "source": [
    "## Creating new cross_sections\n",
    "\n",
    "You can create functions that return a cross_section in 2 ways:\n",
    "\n",
    "- Customize an existing cross-section for example `gf.cross_section.strip`.\n",
    "- Define a function that returns a cross_section.\n",
    "- Define a CrossSection object.\n",
    "\n",
    "What parameters do `cross_section` take?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "96",
   "metadata": {},
   "outputs": [],
   "source": [
    "help(gf.cross_section.cross_section)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.cross_section import CrossSection, cross_section, xsection\n",
    "from gdsfactory.typings import LayerSpec\n",
    "\n",
    "@xsection\n",
    "def pin(\n",
    "    width: float = 0.5,\n",
    "    layer: LayerSpec = \"WG\",\n",
    "    radius: float = 10.0,\n",
    "    radius_min: float = 5,\n",
    "    layer_p: LayerSpec = (21, 0),\n",
    "    layer_n: LayerSpec = (20, 0),\n",
    "    width_p: float = 2,\n",
    "    width_n: float = 2,\n",
    "    offset_p: float = 1,\n",
    "    offset_n: float = -1,\n",
    "    **kwargs,\n",
    ") -> CrossSection:\n",
    "    \"\"\"Return PIN cross_section.\"\"\"\n",
    "    sections = (\n",
    "        gf.Section(layer=layer_p, width=width_p, offset=offset_p),\n",
    "        gf.Section(layer=layer_n, width=width_n, offset=offset_n),\n",
    "    )\n",
    "\n",
    "    return cross_section(\n",
    "        width=width,\n",
    "        layer=layer,\n",
    "        radius=radius,\n",
    "        radius_min=radius_min,\n",
    "        sections=sections,\n",
    "        **kwargs,\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.straight(cross_section=pin)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99",
   "metadata": {},
   "outputs": [],
   "source": [
    "pin5 = gf.components.straight(cross_section=pin, length=5)\n",
    "pin5.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "100",
   "metadata": {},
   "outputs": [],
   "source": [
    "pin5 = gf.components.straight(cross_section=\"pin\", length=5)\n",
    "pin5.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "101",
   "metadata": {},
   "source": [
    "Finally, you can also pass the dictionary (dict) of most components that define the cross-section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "102",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create our first cross-section\n",
    "s0 = gf.Section(width=0.5, offset=0, layer=(1, 0), name=\"wg\", port_names=(\"o1\", \"o2\"))\n",
    "s1 = gf.Section(width=0.2, offset=0, layer=(3, 0), name=\"slab\")\n",
    "x1 = gf.CrossSection(sections=(s0, s1))\n",
    "\n",
    "# Create the second cross-section that we want to transition to.\n",
    "s0 = gf.Section(width=0.5, offset=0, layer=(1, 0), name=\"wg\", port_names=(\"o1\", \"o2\"))\n",
    "s1 = gf.Section(width=3.0, offset=0, layer=(3, 0), name=\"slab\")\n",
    "x2 = gf.CrossSection(sections=(s0, s1))\n",
    "\n",
    "# To show the cross-sections, let us create two paths and create components by extruding them.\n",
    "p1 = gf.path.straight(length=5)\n",
    "p2 = gf.path.straight(length=5)\n",
    "wg1 = gf.path.extrude(p1, x1)\n",
    "wg2 = gf.path.extrude(p2, x2)\n",
    "\n",
    "# Place both cross-section components and quickplot them.\n",
    "c = gf.Component()\n",
    "wg1ref = c << wg1\n",
    "wg2ref = c << wg2\n",
    "wg2ref.movex(7.5)\n",
    "\n",
    "# Create the transitional cross-section.\n",
    "xtrans = gf.path.transition(cross_section1=x1, cross_section2=x2, width_type=\"linear\")\n",
    "# Create a path for the transitional cross-section to follow.\n",
    "p3 = gf.path.straight(length=15, npoints=100)\n",
    "\n",
    "# Use the transitional cross-section to create a component.\n",
    "straight_transition = gf.path.extrude_transition(p3, xtrans)\n",
    "straight_transition.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "103",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "xtrans = gf.path.transition(\n",
    "    cross_section1=x1, cross_section2=x2, width_type=\"parabolic\"\n",
    ")\n",
    "\n",
    "p3 = gf.path.straight(length=15, npoints=100)\n",
    "\n",
    "\n",
    "straight_transition = gf.path.extrude_transition(p3, xtrans)\n",
    "straight_transition.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "104",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "xtrans = gf.path.transition(cross_section1=x1, cross_section2=x2, width_type=\"sine\")\n",
    "p3 = gf.path.straight(length=15, npoints=100)\n",
    "\n",
    "\n",
    "straight_transition = gf.path.extrude_transition(p3, xtrans)\n",
    "straight_transition.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "105",
   "metadata": {},
   "outputs": [],
   "source": [
    "s = straight_transition.to_3d()\n",
    "s.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "106",
   "metadata": {},
   "source": [
    "The port location, width and orientation remains the same for a sheared component. However, an additional property, `shear_angle` is set to the value of the shear angle. In general, shear ports can be safely connected together."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "107",
   "metadata": {},
   "source": [
    "## bbox_layers vs cladding_layers\n",
    "\n",
    "For extruding waveguides you have two options:\n",
    "\n",
    "1. bbox_layers for squared bounding box.\n",
    "2. cladding_layers for extruding a layer that follows the shape of the path."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "108",
   "metadata": {},
   "outputs": [],
   "source": [
    "xs_bbox = gf.cross_section.cross_section(bbox_layers=((3, 0),), bbox_offsets=(3,))\n",
    "w1 = gf.components.bend_euler(cross_section=xs_bbox)\n",
    "w1.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "109",
   "metadata": {},
   "outputs": [],
   "source": [
    "xs_clad = gf.cross_section.cross_section(cladding_layers=[(3, 0)], cladding_offsets=[3])\n",
    "w2 = gf.components.bend_euler(cross_section=xs_clad)\n",
    "w2.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "110",
   "metadata": {},
   "source": [
    "## Insets\n",
    "\n",
    "It is handy to be able to extrude a `CrossSection` along a `Path`, while each `Section` may have a particular inset relative to the main `Section`. An example of this is a waveguide with a heater."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "111",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "\n",
    "\n",
    "@xsection\n",
    "def xs_waveguide_heater() -> gf.CrossSection:\n",
    "    return gf.cross_section.cross_section(\n",
    "        layer=\"WG\",\n",
    "        width=0.5,\n",
    "        sections=(\n",
    "            gf.cross_section.Section(\n",
    "                name=\"heater\",\n",
    "                width=1,\n",
    "                layer=\"HEATER\",\n",
    "                insets=(1, 2),\n",
    "            ),\n",
    "        ),\n",
    "    )\n",
    "\n",
    "\n",
    "c = gf.components.straight(cross_section=xs_waveguide_heater)\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "112",
   "metadata": {},
   "outputs": [],
   "source": [
    "@xsection\n",
    "def xs_waveguide_heater_with_ports() -> gf.CrossSection:\n",
    "    return gf.cross_section.cross_section(\n",
    "        layer=\"WG\",\n",
    "        width=0.5,\n",
    "        sections=(\n",
    "            gf.cross_section.Section(\n",
    "                name=\"heater\",\n",
    "                width=1,\n",
    "                layer=\"HEATER\",\n",
    "                insets=(1, 2),\n",
    "                port_names=(\"e1\", \"e2\"),\n",
    "                port_types=(\"electrical\", \"electrical\"),\n",
    "            ),\n",
    "        ),\n",
    "    )\n",
    "\n",
    "\n",
    "c = gf.components.straight(cross_section=xs_waveguide_heater_with_ports)\n",
    "c.plot()"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql",
   "encoding": "# -*- coding: utf-8 -*-"
  },
  "kernelspec": {
   "display_name": "gdsfactory",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
